package org.hackystat.projecttrajectory.db;

import java.lang.reflect.Constructor;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.projecttrajectory.resource.estimate.jaxb.ProjectEstimate;
import org.hackystat.projecttrajectory.server.Server;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.users.jaxb.User;
import org.hackystat.utilities.stacktrace.StackTrace;
import static org.hackystat.projecttrajectory.server.ServerProperties.DB_IMPL_KEY;

/**
 * Provides an interface to storage for the resources managed by the
 * ProjectTrajectory. Currently we have one storage mechanisms: a persistent
 * store which is implemented by an embedded Derby database.
 * 
 * @author Cam Moore
 * 
 */
public class DbManager {
  /** The chosen Storage system. */
  private DbImplementation dbImpl;

  /**
   * Creates a new DbManager which manages access to the underlying persistency
   * layer(s). Instantiates the underlying storage system to use.
   * 
   * @param server The Restlet server instance.
   */
  public DbManager(Server server) {
    // Defaults to
    // "org.hackystat.projecttrajectory.db.derby.DerbyImplementation"
    String dbClassName = server.getServerProperties().get(DB_IMPL_KEY);
    Class<?> dbClass = null;
    // First try and find the class specified in projecttrajectory.properties
    try {
      dbClass = Class.forName(dbClassName);
    } catch (ClassNotFoundException e) {
      String msg = "DB error instantiating " + dbClassName + ". Could not find this class.";
      server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
      throw new IllegalArgumentException(e);
    }
    // Next, try to find a constructor that accepts a Server as its parameter.
    Class<?>[] constructorParam = {org.hackystat.projecttrajectory.server.Server.class};
    Constructor<?> dbConstructor = null;
    try {
      dbConstructor = dbClass.getConstructor(constructorParam);
    } catch (Exception e) {
      String msg = "DB error instantiating " + dbClassName + ". Could not find Constructor(server)";
      server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
      throw new IllegalArgumentException(e);
    }
    // Next, try to create an instance of DbImplementation from the Constructor.
    Object[] serverArg = {server};
    try {
      this.dbImpl = (DbImplementation) dbConstructor.newInstance(serverArg);
    } catch (Exception e) {
      String msg = "DB error instantiating " + dbClassName + ". Could not create instance.";
      server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
      throw new IllegalArgumentException(e);
    }
    this.dbImpl.initialize();
  }

  /**
   * Returns the XML EstimateIndex for all Estimates in this server.
   * 
   * @return The XML String containing an index to all project estimates.
   */
  public String getProjectEstimateIndex() {
    return this.dbImpl.getProjectEstimateIndex();
  }

  /**
   * Returns the EstimateIndex instance as XML string, or null if not found.
   * This lists all the estimates for the given Project even if the estimator
   * is not a member of the project?
   * 
   * @param project The project.
   * @return The EstimateIndex XML string, or null.
   */
  public String getProjectEstimateIndex(Project project) {
    return this.dbImpl.getProjectEstimateIndex(project);
  }

  /**
   * Returns the XML EstimateIndex for the given project in this server.
   * 
   * @param projectName the project.
   * @return The XML String containing an index to the project's estimates.
   */
  public String getProjectEstimateIndex(String projectName) {
    return this.dbImpl.getProjectEstimateIndex(projectName);
  }
  /**
   * Returns the EstimateIndex instance as XML string, or null if not found.
   * 
   * @param user The user.
   * @return The ProjectEstimate XML string, or null.
   */
  public String getEstimateIndex(User user) {
    return this.dbImpl.getUserEstimateIndex(user);
  }

  /**
   * Returns the XML EstimateIndex for the given user in this server.
   * 
   * @param user the user's name.
   * @return The XML String containing an index to the user's estimates.
   */
  public String getUserEstimateIndex(String user) {
    return this.dbImpl.getUserEstimateIndex(user);
  }
  /**
   * Returns true if the project has estimates.
   * 
   * @param project The project.
   * @return True if the given project has estimates.
   */
  public boolean hasProjectEstimate(Project project) {
    return this.dbImpl.hasProjectEstimate(project);
  }

  /**
   * Returns the XML string containing the ProjectEstimate for the given user and project.
   * 
   * @param user the user associated with the estimate.
   * @param project the project.
   * @return The XML string representing the estimate.
   */
  public String getProjectEstimate(User user, Project project) {
    return this.dbImpl.getProjectEstimate(user, project);
  }
  
  /**
   * Returns the XML string containing the ProjectEstimate for the given user and project.
   * 
   * @param user the user associated with the estimate.
   * @param project the project name.
   * @return The XML string representing the estimate.
   */
  public String getProjectEstimate(String user, String project) {
    return this.dbImpl.getProjectEstimate(user, project);
  }
  /**
   * Returns true if the given project has an estimate with the given timestamp.
   * 
   * @param project The project.
   * @param timestamp The timestamp.
   * @return True if the project has an estimate with the timestamp.
   */
  public boolean hasProjectEstimate(Project project, XMLGregorianCalendar timestamp) {
    return this.dbImpl.hasProjectEstimate(project, timestamp);
  }

  /**
   * Persists a ProjectEstimate instance.  If ProjectEstimate with this [project, timestamp] 
   * already exists in the storage system, it should be overwritten.
   * @return True if the ProjectEstimate was successfully inserted.
   * @param estimate The ProjectEstimate.
   * @param xmlProjectEstimate The ProjectEstimate marshalled into an XML String.
   * @param xmlEstimateRef The EstimateRef marshalled into an XML String.
   */
  public boolean storeProjectEstimate(ProjectEstimate estimate, String xmlProjectEstimate, 
      String xmlEstimateRef) {
    return this.dbImpl.storeProjectEstimate(estimate, xmlProjectEstimate, xmlEstimateRef);
  }
  
  /**
   * Ensures that the project estimate with the given project and timestamp is no longer present
   * in the db.
   * @param project The project.
   * @param timestamp The timestamp.
   */
  public void deleteProjectEstimate(Project project, XMLGregorianCalendar timestamp) {
    this.dbImpl.deleteProjectEstimate(project, timestamp);
  }
  
  /**
   * Ensures that any project estimate with the given estimator and project name is
   * no longer in the db.
   * 
   * @param estimator the estimator.
   * @param projectName the name of the project.
   */
  public void deleteProjectEstimate(String estimator, String projectName) {
    this.dbImpl.deleteProjectEstimate(estimator, projectName);
  }

}
